/**
 * 
 */
package encodingSimulation;


import java.io.File;

import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashMap;
import java.util.TreeMap;

/**
 * @author a0080081
 * 
 */
public class EncodingImplementation implements EncodingMethods {

	private File FileName;

	private int FragmentSize;

	private double Reliability;

	private int numberOfSourceFragments;
	private int sourceFileLength;

	private double serverAvailabilityProbability;

	private int totalEncodedPackets;

	public double getServerAvailabilityProbability() {
		return serverAvailabilityProbability;
	}

	public void setServerAvailabilityProbability(
			double serverAvailabilityProbability) {
		this.serverAvailabilityProbability = serverAvailabilityProbability;
	}

	public int getSourceFileLength() {
		return sourceFileLength;
	}

	public void setSourceFileLength(int sourceFileLength) {
		this.sourceFileLength = sourceFileLength;
	}

	public int getNumberOfSourceFragments() {
		return numberOfSourceFragments;
	}

	public void setNumberOfSourceFragments(int numberOfSourceFragments) {
		this.numberOfSourceFragments = numberOfSourceFragments;
	}

	public File getFileName() {
		return FileName;
	}

	public void setFileName(File fileName) {
		FileName = fileName;
	}

	public int getFragmentSize() {
		return FragmentSize;
	}

	public void setFragmentSize(int fragmentSize) {
		FragmentSize = fragmentSize;
	}

	public double getReliability() {
		return Reliability;
	}

	public void setReliability(double reliability) {
		Reliability = reliability;
	}

	public int getTotalEncodedPackets() {
		return totalEncodedPackets;
	}

	public void setTotalEncodedPackets(int totalEncodedPackets) {
		this.totalEncodedPackets = totalEncodedPackets;
	}

	@Override
	// @Override
	public void setParameters() {
		double temp=Math.ceil((double)getSourceFileLength() / getFragmentSize());
		setNumberOfSourceFragments((int)temp);
		System.out.println(getNumberOfSourceFragments());
		double delta = Math.pow(1 - getReliability(), -1);
		// System.out.println(delta);
		double E = Math.log(delta) / Math.log(2);
		// System.out.println(E);
		int N = (int) (getNumberOfSourceFragments() + Math.round(E));
		// System.out.println(N);
		setTotalEncodedPackets((int) (N / getServerAvailabilityProbability()));
		System.out.println(getTotalEncodedPackets());
	}

	@Override
	// TODO changing the code to store encoded code into integer array rather
	// than
	public ArrayList<BitSet> createEncodedPackets(
			ArrayList<BitSet> sourceFragments, int[][] generatorMatrix) {
		ArrayList<BitSet> encodedFragments = new ArrayList<BitSet>();

		for (int i = 0; i < getTotalEncodedPackets(); i++) {
			int z = 0;
			BitSet temp = null;
			for (int j = 0; j < getNumberOfSourceFragments(); j++) {

				if (generatorMatrix[j][i] == 1) {
					if (z == 0) {
						temp = (BitSet) sourceFragments.get(j).clone();
						z = z + 1;
					} else {
						temp.xor(sourceFragments.get(j));
						z = z + 1;
					}
				}

			}
			// System.out.println(" encoded bitset"+temp);
			encodedFragments.add((BitSet) temp);
			temp=null;
		}
		return encodedFragments;
	}

	@Override
	/*
	 * Calculate the optimal fragment size
	 */
	public int calculateFragmentSize() {
		int sourceFileSize = getSourceFileLength();
		int numberOfSourceFrag = getNumberOfSourceFragments();
		int fragSize = 0;
		int modFragSize = 0;

		if (sourceFileSize > MINFILESIZE
				&& sourceFileSize < FILESIZEFOROPTIMALFRAG) {
			/*
			 * for file size greater than 40KB and less than 512KB fragment size
			 * is equal to the CLUSTER_SIZE
			 */
			fragSize = CLUSTERSIZE;

		} else {
			/*
			 * file size greater than 512 KB perform the below operation
			 */
			if (sourceFileSize >= FILESIZEFOROPTIMALFRAG) {
				/*
				 * if file size is a multiple of 2 then fragSize is
				 * filesize/128;
				 */
				fragSize = sourceFileSize / numberOfSourceFrag;
				modFragSize = fragSize % CLUSTERSIZE;
				if (modFragSize == 0) {
					System.out.println(" no problem here");
				} else {
					/*
					 * calculate the optimal number of fragments
					 */

					numberOfSourceFrag = optimalFragmentSize();
					setNumberOfSourceFragments(numberOfSourceFrag);
					fragSize = sourceFileSize / getNumberOfSourceFragments();
					System.out
							.println(" modify the number of source frag and determine opt frag size");
				}
			}
		}
		return fragSize;
	}

	/*
	 * code to find the optimal number of fragments
	 */
	private int optimalFragmentSize() {
		TreeMap<Integer, Integer> sortedMap = null;
		TreeMap<Integer, Integer> decreaseMap = null;
		/*
		 * initialization
		 */
		int sourceFileSize = getSourceFileLength();
		int numberOfSourceFrag = getNumberOfSourceFragments();
		int fragSize =  sourceFileSize / numberOfSourceFrag;
		int optNumberOfFrag = 0;
		int val1 = 0, val2 = 0;
		/*
		 * fragSize%CLUSTERSIZE will be the extra space occupied in the new
		 * cluster modfrag(wastage)=CLUSTERSIZE-occupied aim is to reduce this
		 * as much as possible
		 */
		int modFrag = CLUSTERSIZE - (fragSize % CLUSTERSIZE);
		/*
		 * HashMap<int modFragSize, int numSrcFrg > map = new HashMap<K, V>();
		 * Since we are not sure which fragment size/ number of fragments will
		 * be optimal around 128 we try to find the size by increasing the
		 * number of source fragments and by reducing the number of source
		 * fragments around INITIAL
		 */
		HashMap<Integer, Integer> increasingFragNumber = new HashMap<Integer, Integer>();
		while (modFrag >= WASTAGEVALUE ) {
			numberOfSourceFrag++;
			fragSize =sourceFileSize / numberOfSourceFrag;
			/*
			 * calculate new wastage after increasing number of source frags
			 */
			modFrag = CLUSTERSIZE - (fragSize % CLUSTERSIZE);
			increasingFragNumber.put(modFrag, numberOfSourceFrag);
			// System.out.println(" decresing"+numberOfSourceFrag+" "+fragSize);
		}
		/*
		 * initialize again and find the optimal fragment number by reducing
		 * against INITIAL
		 */
		sourceFileSize = getSourceFileLength();
		numberOfSourceFrag = getNumberOfSourceFragments();
		fragSize = sourceFileSize / numberOfSourceFrag;
		modFrag = CLUSTERSIZE - (fragSize % CLUSTERSIZE);
		HashMap<Integer, Integer> decreasingFragNumber = new HashMap<Integer, Integer>();
		while (modFrag > WASTAGEVALUE && numberOfSourceFrag>64) {
			numberOfSourceFrag--;
			fragSize = sourceFileSize / numberOfSourceFrag;
			modFrag = CLUSTERSIZE - (fragSize % CLUSTERSIZE);
			/*
			 * adding the least wastage value and the number of fragments
			 * corresponding to that into a map
			 */
			decreasingFragNumber.put(modFrag, numberOfSourceFrag);
			// System.out.println(" increasing"+numberOfSourceFrag+" "+fragSize);
		}
		// TODO: handle when both ash maps are empty
		/*
		 * sort both the maps to obtain the numberOfSrcFrag depending on the
		 * least wastage value
		 */
		if (!increasingFragNumber.isEmpty()) {
			sortedMap = new TreeMap<Integer, Integer>(increasingFragNumber);
			val1 = sortedMap.firstKey();
			System.out.println("increase " + val1 + " " + sortedMap.get(val1));
		}
		if (!decreasingFragNumber.isEmpty()) {
			decreaseMap = new TreeMap<Integer, Integer>(decreasingFragNumber);
			val2 = decreaseMap.firstKey();
			System.out
					.println(" decrease" + val2 + " " + decreaseMap.get(val2));
		}
		/*
		 * cases when one of the TreeMaps are empty when both are available and
		 * when both are empty
		 */
		// TODO what to do when the resulting number of source fragments causes
		// too much redundancy

		if (!increasingFragNumber.isEmpty() 
				&& decreasingFragNumber.isEmpty()) {
		
			optNumberOfFrag = sortedMap.get(val1);
		
		} else if (!decreasingFragNumber.isEmpty()
				&& increasingFragNumber.isEmpty()) {		
			optNumberOfFrag = sortedMap.get(val2);

		} else {
			/*
			 * selecting the numberOfSourceFrag which is closet to INITIAL so
			 * that redundancy is less
			 */
			if (decreasingFragNumber.isEmpty() && increasingFragNumber.isEmpty()) {
				optNumberOfFrag = getNumberOfSourceFragments();
			} else {
				if (Math.abs(sortedMap.get(val1) - getNumberOfSourceFragments()) < Math
						.abs(decreaseMap.get(val2) - getNumberOfSourceFragments())) {
					optNumberOfFrag = sortedMap.get(val1);
				} else {
					optNumberOfFrag = decreaseMap.get(val2);
				}
			}
			
		}
			
			
			
//		}
//		if (decreasingFragNumber.isEmpty() && increasingFragNumber.isEmpty()) {
//			optNumberOfFrag = getNumberOfSourceFragments();
//		}

		// System.out.println(" " + optNumberOfFrag);
		return optNumberOfFrag;
	}

	// String[] encodedPackets = new String[totalEncodedPackets];
	// for (int i = 0; i <getTotalEncodedPackets(); i++) {
	// int z = 0;
	//
	// byte[] temp= new byte[getFragmentSize()];
	// // System.out.println(" value of i"+i);
	// for (int j = 0; j <getNumberOfSourceFragments(); j++) {
	// if (generatorMatrix[j][i] == 1) {
	// // System.out.println(" in the loop is gen has 1 in the entry");
	// if (z == 0) {
	// //
	// System.out.println("in the loop when z=0 "+sourceFragments[j].length());
	// temp = sourceFragments[j].getBytes();
	// // System.out.println(" after copy"+temp[z].length());
	// z=z+1;
	// } else {
	// // System.out.println("in the else loop");
	// // System.out.println(" "+sourceFragments[j].length());
	// // System.out.println(" "+temp[z-1].length());
	// StringBuilder sb = new StringBuilder();
	// byte[] temp2 = new byte[getFragmentSize()];
	// byte[] swapTemp=new byte[getFragmentSize()];
	// temp2=sourceFragments[j].getBytes();
	// for (int k = 0; k < temp2.length && k < temp.length; k++){
	// swapTemp[k] =(byte)( (temp[k] ^ temp2[k]));
	// }
	// temp = swapTemp;
	// z = z + 1;
	// }
	// }
	// }
	// if(z>=1){
	// System.out.println(" value of z is"+temp);
	// //set(2);
	// encodedPackets[i] = new String(temp);//.toString();
	// }
	// // System.out.println(encodedPackets[i]);
	// }
	// return encodedPackets;
	// }

}
